package com.laofang.spring.formework.context.support;

import com.laofang.spring.formework.annotion.LFAutowired;
import com.laofang.spring.formework.annotion.LFController;
import com.laofang.spring.formework.annotion.LFService;
import com.laofang.spring.formework.beans.LFBeanDefinition;
import com.laofang.spring.formework.beans.LFBeanWrapper;
import com.laofang.spring.formework.beans.config.LFBeanPostPocessor;
import com.laofang.spring.formework.core.LFBeanFactory;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author ：laofang
 * @date ：Created in 2021/3/30 16:44
 */
public class LFApplicationContext extends LFDefaultListableBeanFactory implements LFBeanFactory {

    private String[] configLocations;

    private LFBeanDefinitionReader reader;

    //单例的ioc容器
    private Map<String,Object> factoryBeanObejctCache=new ConcurrentHashMap<>();

    //通用的IOC容器
    private Map<String, LFBeanWrapper> factoryBeanInstanceCache=new ConcurrentHashMap<>();

    public LFApplicationContext(String[] configLocations) {
        this.configLocations = configLocations;
        try {
            refresh();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void refresh() throws Exception {
        //定义配置文件
        this.reader=new LFBeanDefinitionReader(this.configLocations);
        //加载配置文件，扫描相关类，把它们封装成beandefinition
        List<LFBeanDefinition> beanDefinitions= reader.loadBeanDefinitions();
        //注册，把配置信息放到容器里边
        doRegisterBeanDefinition(beanDefinitions);
        //把不是延迟加载的类，提前初始化
        doAutowired();


    }

    private void doRegisterBeanDefinition(List<LFBeanDefinition> beanDefinitions) throws Exception {
        for (LFBeanDefinition beanDefinition : beanDefinitions) {
            if (super.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
                throw new Exception("the"+beanDefinition.getFactoryBeanName()+"is exists");
            }
            super.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(),beanDefinition);
        }
        //到这位置，容器初始化完毕
    }

    //只处理非延时加载的情况
    private void doAutowired() {
        for (Map.Entry<String, LFBeanDefinition> beanDefinition : super.beanDefinitionMap.entrySet()) {
            if (beanDefinition.getValue().isLazyInit()) {
                try {
                    getBean(beanDefinition.getKey());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }
    }

    @Override
    public Object getBean(Class<?> beanClass) throws Exception {
        //获取beanName
        String beanName=toLowerFirstCase(beanClass.getSimpleName());
        return getBean(beanName);
    }

    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        //因为大小写字符的ASCII码相差32，且大写的值要小
        chars[0]+=32;
        return String.valueOf(chars);
    }

    /**
     * 依赖注入，从这里获取beanDefinition信息
     * 然后通过反射获取一个实例对象，并返回
     * Spring的做法是不会将原始对象放出去，而会用到一个BeanWrapper来进行一次封装
     * 装饰器模式
     * 1.保存原来的oop关系
     * 2.需要对它进行扩展增强（为以后aop打基础）
     * @param name
     * @return
     * @throws Exception
     */
    @Override
    public Object getBean(String name) throws Exception {
        LFBeanDefinition beanDefinition = super.beanDefinitionMap.get(name);
        //生成通知事件
        LFBeanPostPocessor beanPostPocessor = new LFBeanPostPocessor();
        Object instance= instantiateBean(beanDefinition);
        if (null==instance) {
            return null;
        }
        //在实例化之前，调用一次
        beanPostPocessor.postProcessBeforeInitialization(instance,name);
        LFBeanWrapper beanWrapper = new LFBeanWrapper(instance);
        this.factoryBeanInstanceCache.put(name,beanWrapper);
        //在实例初始化以后再调用一次
        beanPostPocessor.postProcessAfterInitialization(instance,name);
        //给实例对象填充数据
        populateBean(name,instance);
        //通过这样调用，相当于给我们留有可操纵的空间
        return this.factoryBeanInstanceCache.get(name).getWrappedInstance();
    }

    private void populateBean(String name, Object instance) {
        Class<?> instanceClass = instance.getClass();
        if (!(instanceClass.isAnnotationPresent(LFController.class)||
                instanceClass.isAnnotationPresent(LFService.class))) {
            return;
        }
        Field[] fields = instanceClass.getDeclaredFields();
        try {
            for (Field field : fields) {
                if (!field.isAnnotationPresent(LFAutowired.class)) {
                    continue;
                }
                LFAutowired annotation = field.getAnnotation(LFAutowired.class);
                String autowareName = annotation.value();
                if (autowareName.equals("")) {
                    autowareName=field.getType().getSimpleName();
                }
                autowareName=toLowerFirstCase(autowareName);
                field.setAccessible(true);
                LFBeanWrapper beanWrapper = this.factoryBeanInstanceCache.get(autowareName);
                Object obj=null;
                if (null!=beanWrapper) {
                    obj=beanWrapper.getWrappedInstance();
                }else {
                    obj=getBean(autowareName);
                }
                field.set(instance, obj);

            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Object instantiateBean(LFBeanDefinition beanDefinition) {
        Object instance=null;
        String beanClassName = beanDefinition.getBeanClassName();
        instance = this.factoryBeanObejctCache.get(beanClassName);
        try {
            if (null==instance) {
                Class<?> beanClass = Class.forName(beanClassName);
                instance= beanClass.newInstance();
                this.factoryBeanObejctCache.put(beanClassName, instance);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        return instance;
    }

    public String[] getBeanDefinitionNames(){
        return super.beanDefinitionMap.keySet().toArray(new String[super.beanDefinitionMap.size()]);
    }

    public int getBeanDefinitionCount(){
        return super.beanDefinitionMap.size();
    }

    public Properties getConfig(){
        return this.reader.getConfig();
    }
}
